package ctxSemanticsGen;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;

import cop.publ.Layer;




/**
 * Class to perform common utility operations, such as compare two methods
 * using reflection to know if they are one a partial definition of the other.
 * 
 * @author Guido Salvaneschi
 *
 */
public class Utility {

	static Logger logger = Logger.getLogger(Utility.class);
	static{ logger.setLevel(Level.OFF); }
	

	/**
	 * Returns true if the method mth is a pertial definition of the base method 
	 * advisedMth.
	 * @param advisedMth
	 * @param mth
	 * @return
	 */
	public static boolean isBaseMethodOfInLayerMethod(Method advisedMth,
			                                          Method mth){
		
		logger.debug("Comparing:");
		logger.debug(advisedMth.toString());
		logger.debug(mth.toString());
		logger.debug(mth.toGenericString());

		logger.debug(advisedMth.getGenericParameterTypes().getClass());

		// Fetch the info on the advised method
		advisedMth.getGenericExceptionTypes();

		Class[] advMthParamTypes = (java.lang.Class[]) advisedMth.getParameterTypes();
		Class advMthReturnType = (java.lang.Class) advisedMth.getReturnType();
		String advMthName = advisedMth.getName();


		// Fetch the infos on the method
		mth.getGenericExceptionTypes();
		Class[] classMthParamTypes = (java.lang.Class<Object>[]) mth.getParameterTypes();
		Class classMthReturnType = (java.lang.Class) mth.getReturnType();
		String classMthName = mth.getName();


		// Now compare	
		if( !(classMthName.startsWith(advMthName)
				&& classMthName.endsWith("Layer")) ){
			logger.debug("Name not matching");
			return false;
		}
		
		if( classMthName.equals(advMthName)){ // Avoid to catch the same method, need really ?
			logger.debug("");
			return false;
		}

		if( ! Arrays.equals(classMthParamTypes,advMthParamTypes) ){
			logger.debug("Param types not matching");
			return false;
		}

		if( ! classMthReturnType.equals(advMthReturnType) ){
			logger.debug("Return types not matching");
			return false;
		}

		logger.debug("Match!");
		return true;
	}
	
	
	/**
	 * Returns true if the method m is a partial definition. This method must
	 * be used for a class which does not declare a mapping between local layer
	 * names and global names in the LayeredClass annotation.
	 * 
	 * @param m The method to test check.
	 * @return
	 */
	public static boolean isInLayerMethodNoMap(Method m){
		return m.getName().contains("Layer");
	}
	
	
	/**
	 * Returns true if the method m is a partial definition according to the
	 * mapping between local layer names and global names in the LayeredClass
	 * annotation. The mapping must be passed as a second parameter.
	 * 
	 * @param m The method to test check.
	 * @param l a list containing the mapping the first string of the element
	 * must be the local name for the layer, the second the global
	 * fully-qualified name. E.g. {"LayerA","pack1.subpack.A"}.
	 * 
	 * @return
	 */
	public static boolean isInLayerMethod(Method m, List<String[]> l){
		
		int resp = 0;
		for(String[] s: l){ if( m.getName().endsWith(s[0]) ){ resp++; } }
		
		if(resp > 1){ /* Exception if more than one match found */
			throw new RuntimeException("According to the given mapping the" +
					"method" + m.getName() + "is defined in more then " +
							"one layer"); 
		}
		return (resp > 0);
	}
	
	
	
	/**
	 * Returns the layer name for the partial defintion baseMethodName.</br>
	 * E.g. layerForName("print","printLayerAAA") gives AAA.
	 * </br></br>
	 * Use layerForName(String methodName, String baseMethodName, 
	 * List<String[]> l ) instead.
	 * 
	 * @param methodName
	 * @param baseMethodName
	 * @return
	 */
	@Deprecated
	public static String layerForName(String methodName, String baseMethodName){
		String n1 = methodName.replace("Layer","");
		String n2 = n1.replace(baseMethodName,"");
		return n2;
	}
	
	
	/**
	 * Returns the fully qualified layer name for the partial definition 
	 * baseMethodName.</br>
	 * E.g. layerForName("print","printLayerAAA") gives a.b.AAA. The mapping 
	 * must be passed as a parameter.
	 * 
	 * @param methodName
	 * @param l a list containing the mapping the first string of the element
	 * must be the local name for the layer, the second the global
	 * fully-qualified name. E.g. {"LayerA","pack1.subpack.A"}.
	 * @return
	 */
	public static String layerForName(String methodName, List<String[]> l){
		int check = 0; // TODO: is this check really needed at this point ?
		String layer = ""; 
		
		for(String[] s: l){ 
			if( methodName.endsWith(s[0]) ){ 
				check++;
				layer = s[1];
			} 
		}
		
		if(check > 1){ /* Exception if more than one match found */
			throw new RuntimeException("In the given mapping the method" + 
					methodName + "is defined in more than one layer"); 
		}
		return layer;
	}
	
	
	
	/**
	 * Returns a unique and valid identifier for the LayerWrapper associated
	 * to a given layerName.
	 * 
	 * @param layerName
	 * @return
	 */
	public static String buildUniqueLayerWrapperName(String layerName){
		String s = layerName + "LayerWrapper";
		return s.replace(".", "_"); // dots in packages
	}
	
	/**
	 * Returns the name of the class containing all the methods declared inside
	 * the give layer. Used to avoid hardcoded names.
	 * 
	 * Use fully qualified names with buildUniqueLayerWrapperName instead
	 * 
	 * @param layerName
	 * @return
	 */
	@Deprecated
	public static String getLayerWrapperName(String layerName){
		return layerName + "LayerWrapper";
	}
	
	
	
	
	/**
	 * Returns a unique string identifier for the method. The returned 
	 * string is also valid Java method identifier.
	 * 
	 * @param m
	 * @return
	 */
	public static String buildUniqueMethodName(Method m){
		
		String s = m.toGenericString();
		 
		s = s.replace(" ", "____"); // separates modifiers and ret val
		 
		s = s.replace(".", "_"); // dots in packages
		 
		s = s.replace("(", "$"); // calls
		s = s.replace(")", "$");
		 
		s = s.replace(",", "$"); // many parameters
		 
		s = s.replace("[", "$$"); // arrays
		s = s.replace("]", "$$");
		
		s = s.replace("<", "$$$"); // generics
		s = s.replace(">", "$$$");
		
		return s;
		 
	}
	
	

	
	public static final String BASE_LAYER_NAME = "Base";
	
	/**
	 * Use fullyQualifiedlayerNameForLayerClass instead!
	 * @param layerClass
	 * @return
	 */
	@Deprecated
	public static String layerNameForLayerClass(Class layerClass){
		// TODO: does not care of the package, change this
		String s = layerClass.getName();
		String[] sp = s.split("\\.");
		return sp[sp.length-1];
		
	}
	
	/**
	 * TODO: change the name 
	 * @param layerClass
	 * @return
	 */
	public static String fullyQualifiedlayerNameForLayerClass(Class layerClass){
		String s = layerClass.getName();
		return s;
		
	}
	
	/**
	 * Use getFullLayerWrapperName instead
	 * 
	 * @param layerClass
	 * @return
	 */
	@Deprecated
	public static String getLayerWrapperName(Class layerClass){
		String s = layerNameForLayerClass(layerClass);
		return getLayerWrapperName(s);
	}
	
	
	public static String getFullLayerWrapperName(Class layerClass){
		String s = fullyQualifiedlayerNameForLayerClass(layerClass);
		return buildUniqueLayerWrapperName(s);
	}
	
	
	
	
	/**
	 * Takes as input an element of a call to 
	 * Type[] Method.getGenericParameterTypes() on which toString() was called.
	 * For example these conversions apply:
	 * 
	 * class java.lang.String -> java.lang.String
	 * int -> int
	 * java.lang.String[] -> java.lang.String[]
	 * java.util.List<java.lang.String> -> java.util.List<java.lang.String>
	 * 
	 * @param s
	 * @return
	 */
	public static String sanitizeGenericParameterType(String s){
		int i = s.indexOf(' ');
		return s.subSequence(i+1,s.length()).toString();
	}
	
	
	
}





